package cn.imatu.framework.log;

import cn.hutool.core.util.ClassUtil;
import cn.imatu.framework.log.annotation.*;
import cn.imatu.framework.tool.core.*;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.ttl.TransmittableThreadLocal;
import lombok.Data;

import java.util.Objects;

/**
 * 当使用 {@link OperateLog#content()} 指定spel表达式时候, 有时候我们为了获取指定的变量需要在 controller 入参定义一个额外的
 * 对象, 然后查询数据库, 将获取到的值设置到对象中, 最终方法执行完后, spel会解析到设置的变量值
 *
 * 对于保存或者更新场景, 一般不需要, 但是对于删除场景, 我们常见删除入参都是根据id取删除, 这样的话就需要设置一个日志参数对象放到接口入参中,
 * 然后在删除之前根据id查询一次, 将值设置到对象中
 *
 * 为了避免对已有业务删除等service方法入参进行改造/多次查询(不改造service删除方法入参, 直接在controller根据id查询), 最小入侵。
 *
 * 需要在删除的方法中设置对应的变量, 然后在{@link OperateLog#content()} , 以 {{ #ctx_xxx }} 的方式取值,
 *
 * 注意如果controller的入参中包含了 context 入参变量名, 则无效
 *
 * @author shenguangyang
 */
@Data
public class LogContext {
    private static final TransmittableThreadLocal<LogContext> TTL = new TransmittableThreadLocal<>();

    private final JSONObject params = new JSONObject();
    private String delimiter;

    public static void addParams(String key, Object value) {
        addParams(key, value, "");
    }

    /**
     * 只有当controller方法上添加 {@link OperateLog} 注解才会生效
     * @param delimiter 分隔符, 当多次添加时, 采用的分隔符, 只对基本类型有效
     */
    public static void addParams(String key, Object value, String delimiter) {
        if (Objects.isNull(value) && StringUtils.isEmpty(key)) {
            return;
        }
        LogContext logContext = TTL.get();
        if (Objects.isNull(logContext)) {
            return;
        }
        logContext.setDelimiter(delimiter);

        if (StringUtils.isNotEmpty(delimiter) && (ClassUtil.isBasicType(value.getClass()) || value instanceof String)) {
            Object oldValue = logContext.getParams().get(key);
            String newValueStr = oldValue == null ? value.toString() : oldValue + delimiter + value;
            logContext.getParams().put(key, newValueStr);
        } else {
            logContext.getParams().put(key, value);
        }
    }

    protected static JSONObject params() {
        LogContext logContext = TTL.get();
        return logContext == null ? new JSONObject() : logContext.getParams();
    }

    protected static void initParams() {
        TTL.set(new LogContext());
    }

    protected static void clean() {
        TTL.remove();
    }
}
